package com.hbwxz.oauth2.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * Oauth2配置类
 *
 * @author shenzw
 * @date 2023/8/23
 */
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {

    private final DataSource dataSource;

    private final UserDetailsService userDetailsService;

    private final AuthenticationManager manager;

    /**
     * oauth2 是为了生成 token 令牌的，token 令牌需要存储到哪里呢？所以需要先解决存储问题
     */
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * token 是不是得有过期时间啊？过期时间咋搞？
     * oauth 的默认 token 的过期时间是 12 个小时，如果想自定义它的过期时间，我们需要用到 defaultTokenService，并进行 set
     */
    @Bean
    @Primary
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setAccessTokenValiditySeconds(30 * 24 * 3600); // 30天过期
        services.setTokenStore(tokenStore());
        return services;
    }


    /**
     * 关心一下我们的 client details 中的内容：client id 和 client secret，从哪里搞？
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 我们可以通过 ClientDetailsServiceConfig 将我们的 ClientDetailsService 设置到 oauth 中
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
    }

    /**
     * 用户密码和 client secret 是不是不能存储明文啊，是不是得搞一个加密方式？
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 如果企业要求自己的加密算法，可以通过这种形式进行 encode 以及校验是否相符
//        return new PasswordEncoder() {
//            @Override
//            public String encode(CharSequence rawPassword) {
//                return null;
//            }
//
//            @Override
//            public boolean matches(CharSequence rawPassword, String encodedPassword) {
//                return false;
//            }
//        };
        return new BCryptPasswordEncoder();
    }

    /**
     * 添加自定义的安全配置，你可以选择不添加；往往我们蒋这个配置用于：放开一些接口的查询权限，比如说 checkToken 接口
     *
     * @param security AuthorizationServerSecurityConfigurer
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients() // 可以进行表单验证
                .checkTokenAccess("permitAll()"); // checkToken
    }

    /**
     * 至少这个接口要把 UserDetailsService 安排了
     *
     * @param endpoints AuthorizationServerEndpointsConfigurer
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.userDetailsService(userDetailsService);
        endpoints.tokenServices(defaultTokenServices());
        endpoints.authenticationManager(this.manager);
        endpoints.tokenStore(tokenStore());
    }
}
